import { IPrefab, PrefabPath, PrefabType } from "../../config/PrefabPath";
import LoadingTip from "../../game/common/LoadingTip";
import LayerBase from "../common/LayerBase";
import DialogBase from "../common/DialogBase";
import ToastBase from "../common/ToastBase";
import GuideManager from "./GuideManager";

/**
 * 弹窗管理器
 */
export default class UIManager {

    /** 预制体表 */
    public static get prefabMap() { return this._prefabMap; }
    private static _prefabMap: Map<string, cc.Prefab> = new Map<string, cc.Prefab>();

    /** 节点表 */
    public static get nodeMap() { return this._nodeMap; }
    private static _nodeMap: Map<string, cc.Node> = new Map<string, cc.Node>();

    /** 等待队列 */
    public static get queue() { return this._queue; }
    private static _queue: DialogRequest[] = [];

    /** 当前弹窗 */
    public static get current() { return this._current; }
    private static _current: DialogRequest = null;

    /** 用于存放弹窗节点的容器节点（默认为 Canvas） */
    public static dialogContainer: cc.Node = null;

    /** 用于存放面板节点的容器节点（默认为 Canvas） */
    public static layerContainer: cc.Node = null;

    /** 用于存放toast节点的容器节点（默认为 Canvas） */
    public static toastContainer: cc.Node = null;

    /** 用于存放loading节点的容器节点（默认为 Canvas） */
    public static loadingContainer: cc.Node = null;


    /** 用于存放loading节点的容器节点（默认为 Canvas） */
    public static guideContainer: cc.Node = null;

    /**
     * 悬浮通知提醒消息
     * @param {string} text
     */
    public static async showToast(text: string) {
        await this.show(PrefabPath.toastTipLayer, text);
    }

    /**
     * 在加载过场中显示loading界面
     */
    public static async showLoading(): Promise<LoadingTip> {
        this.loadingContainer.active = true;
        let prefab = PrefabPath.loading
        return new Promise(async (resolve) => {
            const path = prefab.path;
            let node = this.getNodeFromCache(path);
            if (!cc.isValid(node)) {
                // 等待加载
                const prefab = await this.load(path);
                node = cc.instantiate(prefab);
            }
            const loadingJs = node.getComponent(LoadingTip);
            node.setParent(this.loadingContainer || cc.Canvas.instance.node);
            node.setSiblingIndex(cc.macro.MAX_ZINDEX);
            loadingJs.show();
            resolve(loadingJs);
        })
    }

    /**
     * 加载完成 隐藏loading界面
     */
    public static hideLoading() {
        this.loadingContainer.active = false;
    }

    /**
     * 展示弹窗
     * @param path 弹窗预制体相对路径
     * @param options 弹窗数据
     * @param mode 缓存模式
     */
    public static show<Options>(prefabCfg: IPrefab, options: Options = null, mode: DialogCacheMode = DialogCacheMode.Normal): Promise<DialogShowResult> {
        return new Promise(async res => {
            let path: string = prefabCfg.path;
            let type: PrefabType = prefabCfg.type;
            let noLoading: boolean = prefabCfg.noLoading;

            this._current = { path, options, mode };
            // 先在缓存中获取
            let node = this.getNodeFromCache(path);
            // 缓存中没有，动态加载预制体资源
            if (!cc.isValid(node)) {
                let prefab: cc.Prefab
                if (noLoading) {
                    prefab = await this.load(path);
                } else {
                    let loading = await this.showLoading();
                    prefab = await this.load(path, loading);
                    await this.hideLoading();
                }
                // 加载失败（一般是路径错误导致的）
                if (!cc.isValid(prefab)) {
                    cc.warn('[UIManager]', '弹窗加载失败', path);
                    this._current = null;
                    res(DialogShowResult.Fail);
                    return;
                }
                // 实例化节点
                node = cc.instantiate(prefab);
            }
            // 保存节点
            this._current.node = node;
            // 获取继承自 DialogBase 的弹窗组件
            const dialogJs = node.getComponent(DialogBase);
            const toastJs = node.getComponent(ToastBase);
            if (dialogJs) {
                // 添加到场景中
                switch (type) {
                    case PrefabType.Layer:
                        node.setParent(this.layerContainer || cc.Canvas.instance.node);
                        break;
                    case PrefabType.Dialog:
                        node.setParent(this.dialogContainer || cc.Canvas.instance.node);
                        break;
                    case PrefabType.Guide:
                        node.setParent(this.dialogContainer || cc.Canvas.instance.node);
                        break;
                }
                if (toastJs) {
                    node.setParent(this.toastContainer || cc.Canvas.instance.node);
                }
                res(DialogShowResult.Done);
                // 显示在最上层
                // node.setSiblingIndex(cc.macro.MAX_ZINDEX);
                this._current.dialog = dialogJs;
                this._queue.push(this._current)
                // 设置完成回调
                dialogJs.setFinishCallback(async () => {
                    this.recycle(path, node, mode);
                });
                dialogJs.show(options);
                if (dialogJs.node.name != 'guideLayer') {
                    GuideManager.Ins.guideTrigger()
                }
            } else {
                // 添加到场景中
                node.setParent(this.dialogContainer || cc.Canvas.instance.node);
                // 显示在最上层
                node.setSiblingIndex(cc.macro.MAX_ZINDEX);
                // 没有弹窗组件则直接打开节点
                node.active = true;
                res(DialogShowResult.Dirty);
            }
        });
    }
    /**
     * 根据弹窗配置关闭弹窗或者面板
     * @param path 
     */
    public static hide(prefab: IPrefab) {
        let path: string = prefab.path;
        let needClose = this.removeFromQueue(path);
        if (needClose && needClose.length) {
            let needHide: DialogRequest = needClose[0];
            if (needHide.dialog) {
                needHide.dialog.hide();
            } else if (needHide.node) {
                needHide.node.destroy();
                this.release(needHide.path);
            }
        }
        // GuideManager.Ins.guideTrigger()
    }

    /**
     * 从队列中移除
     */
    private static removeFromQueue(path: string): DialogRequest[] {
        let idx = -1;
        this._queue.forEach((pop, index) => {
            if (pop.path == path) {
                idx = index;
            }
        })
        if (idx > -1) {
            return this._queue.splice(idx, 1);
        }
        return null;
    }

    /**
     * 从缓存中获取节点
     * @param path 弹窗路径
     */
    private static getNodeFromCache(path: string): cc.Node {
        // 从节点表中获取
        if (this._nodeMap.has(path)) {
            const node = this._nodeMap.get(path);
            if (cc.isValid(node)) {
                return node;
            }
            this._nodeMap.delete(path);
        }
        // 从预制体表中获取
        if (this._prefabMap.has(path)) {
            const prefab = this._prefabMap.get(path);
            if (cc.isValid(prefab)) {
                return cc.instantiate(prefab);
            }
            this._prefabMap.delete(path);
        }
        // 无
        return null;
    }

    /**
     * 回收弹窗
     * @param path 弹窗路径
     * @param node 弹窗节点
     * @param mode 缓存模式
     */
    private static recycle(path: string, node: cc.Node, mode: DialogCacheMode): void {
        switch (mode) {
            case DialogCacheMode.Once:
                node.destroy();
                if (this._nodeMap.has(path)) {
                    this._nodeMap.delete(path);
                }
                this.release(path);
                break;
            case DialogCacheMode.Normal:
                node.destroy();
                if (this._nodeMap.has(path)) {
                    this._nodeMap.delete(path);
                }
                break;
            case DialogCacheMode.Frequent:
                node.removeFromParent(false);
                if (!this._nodeMap.has(path)) {
                    this._nodeMap.set(path, node);
                }
                break;
        }
    }

    /**
     * 加载并缓存弹窗预制体资源
     * @param path 弹窗路径
     */
    public static load(path: string, progressNode?: LoadingTip): Promise<cc.Prefab> {
        return new Promise(res => {
            cc.resources.load(path, async (finish, total) => {
                if (progressNode) {
                    progressNode.updateProgress(finish / total)
                }
            }, (error: Error, prefab: cc.Prefab) => {
                if (error) {
                    res(null);
                } else {
                    prefab.addRef();                    // 增加引用计数
                    this._prefabMap.set(path, prefab);  // 缓存预制体
                    res(prefab);
                }
            });
        });
    }

    /**
     * 尝试释放弹窗资源（注意：弹窗内部动态加载的资源请自行释放）
     * @param path 弹窗路径
     */
    public static release(path: string): void {
        // 移除节点
        let node = this._nodeMap.get(path);
        if (node) {
            this._nodeMap.delete(path);
            if (cc.isValid(node)) {
                node.destroy();
            }
            node = null;
        }
        // 移除预制体
        let prefab = this._prefabMap.get(path);
        if (prefab) {
            this._prefabMap.delete(path);
            prefab.decRef();
            prefab = null;
        }
    }

}
/** 弹窗请求 */
export interface DialogRequest {
    /** 弹窗预制体相对路径 */
    path: string;
    /** 弹窗选项 */
    options: any;
    /** 缓存模式 */
    mode: DialogCacheMode,
    /** 弹窗组件 */
    dialog?: DialogBase,
    /** 弹窗组件 */
    layer?: LayerBase,
    /** 弹窗节点 */
    node?: cc.Node
}

/** 弹窗请求结果 */
export enum DialogShowResult {
    /** 展示成功（已关闭） */
    Done = 1,
    /** 展示失败（加载失败） */
    Fail = 2,
    /** 等待中（已加入等待队列） */
    Wait = 3,
    /** 直接展示（未找到弹窗组件） */
    Dirty = 4
}

/** 弹窗缓存模式 */
export enum DialogCacheMode {
    /** 一次性的（立即销毁节点，预制体资源随即释放） */
    Once = 1,
    /** 正常的（立即销毁节点，但是保留预制体资源） */
    Normal = 2,
    /** 频繁的（只关闭节点，且保留预制体资源） */
    Frequent = 3
}